#pragma once

#include <sys/stat.h>

#include "tester_interface.h"
#include "exports.h"
#include "../db/writable_file.h"

namespace dmock_test {

class WritableFileTest : public Tester {
 public:
  WritableFileTest() : fd_(-1), file_(nullptr) {}
  ~WritableFileTest() {
    if (file_) {
      Close();
    }
  }
  
  void Init() override {
    file_name_ = "output/WRT.wal";
    fd_ = open(file_name_.c_str(), O_CREAT | O_RDWR | O_CLOEXEC | O_TRUNC , 0644);
    assert(fd_ >= 0);
    file_ = new DB_NAME::WritableFile(fd_);
    assert(file_ != nullptr);
    GLOB_LOG_INFO("Init over. fd={}", fd_);
  }

  void Run() override {
    RC ret = RC::SUCCESS;
    struct stat st;
    GLOB_LOG_INFO("[Test.1] Write 1KB.");
    if (OP_FAIL(test_write(1024))) {
      GLOB_LOG_CRITICAL("Fail to write.");
    } else if (stat(file_name_.c_str(), &st) || st.st_size != 0) {
      ret = RC::FAIL;
      GLOB_LOG_CRITICAL("Wal size mismatch, st.st_size={}, answer=0", st.st_size);
    } else if (OP_FAIL(file_->Sync())) {
      GLOB_LOG_CRITICAL("Fail to sync.");
    } else {
      reuse_file();
      GLOB_LOG_INFO("ok");
    }
    assert(IS_SUCC);
    
    GLOB_LOG_INFO("[Test.2] Write 65KB, bs=1KB");
    for (int i = 0; i < 65; ++i) {
      if(OP_FAIL(test_write(1024))) break;
    }
    if (IS_FAIL) {
      GLOB_LOG_CRITICAL("Fail to write.");
    } else if (stat(file_name_.c_str(), &st) || st.st_size != 64 * KB) {
      ret = RC::FAIL;
      GLOB_LOG_CRITICAL("Wal size mismatch, st.st_size={}, answer={}", st.st_size, 64 * KB);
    } else if (OP_FAIL(file_->Sync())) {
      GLOB_LOG_CRITICAL("Fail to sync.");
    } else {
      reuse_file();
      GLOB_LOG_INFO("ok");
    }
    assert(IS_SUCC);

    GLOB_LOG_INFO("[Test.2] Write big size, size=1MB");
    if (OP_FAIL(test_write(1 * MB))) {
      GLOB_LOG_CRITICAL("Fail to write.");
    } else if (stat(file_name_.c_str(), &st) || st.st_size != 1 * MB) {
      ret = RC::FAIL;
      GLOB_LOG_CRITICAL("Wal size mismatch, st.st_size={}, answer={}", st.st_size, 1 * MB);
    } else if (OP_FAIL(file_->Sync())) {
      GLOB_LOG_CRITICAL("Fail to sync.");
    } else {
      reuse_file();
      GLOB_LOG_INFO("ok");
    }
    assert(IS_SUCC);
  }

  void Close() override {
    delete file_;
    file_ = nullptr;
  }
 private:
  RC test_write(size_t n) {
    std::string context(n, '1');
    SView sv(context);
    return file_->Append(sv);
  }
  void reuse_file() {
    lseek(fd_, 0, SEEK_SET);
    ftruncate(fd_, 0);
  }
 private:
  int fd_;
  DB_NAME::WritableFile *file_;
  std::string file_name_;
};

}